﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using PS3Helpers;
using PS3Helpers.Helpers;

namespace PS3Helpers
{
    [Flags]
    public enum PS3ContentType { GameData, GameExec }

    [Flags]
    public enum PS3DrmType { Free, Local }

    [Flags]
    public enum PSPRemotePlayCodec
        {
            Available = 1,
            Mpeg4AvcAac = 2,
            Mpeg4SpAtrac = 4
        }

    public class PS3PackageFile : IDisposable
    {
        private string _currentFilename = null;
        private string _title = "";
        private string _contentId = "";
        private string _ps3SystemVersion = "1.92";
        private PS3ContentType _contentType = PS3ContentType.GameData;
        private string _klicense = "";
        private PS3DrmType _drmType = PS3DrmType.Free;
        private string _appVer = "01.01";
        private string _pkgVer = "01.01";
        private bool _autoIncrement = true;
        private string _logoFilename = null;

        private int _parentalLockLevel;
        private PS3Category _category = PS3Category.HardDiskGame;
        private Set<PS3VideoResolution> _videoResolution = PS3VideoResolution.vr720;
        private Set<PS3AudioFormat> _audioFormat = PS3AudioFormat.LPCM2;
        private Set<PSPRemotePlayCodec> _pspRemotePlayCodec = PSPRemotePlayCodec.Mpeg4AvcAac;

        private VirtualDirectory _rootdir;

        public PS3PackageFile()
        {
            _klicense = "0x" + new Guid().ToString("N");

            _rootdir = new VirtualDirectory(this);
            _rootdir.AddLockedFile("PARAM.SFO");
            _rootdir.AddLockedFile("ICON0.PNG");

            _rootdir.AddDirectory("LICDIR");
            _rootdir.AddDirectory("TROPDIR");
            VirtualDirectory usrdir = _rootdir.AddDirectory("USRDIR");
            usrdir.AddLockedFile("EBOOT.BIN");
        }

        public PS3PackageFile(string filename)
            : this()
        {
            LoadFile(filename);
            _currentFilename = filename;
        }

        // Create a clone of the source PS3Packagefile object
        public PS3PackageFile(PS3PackageFile source)
            : this()
        {
            // If source is null then there's nothing to do here. Everything has already been done by the parameter-less constructor.
            if (source == null)
                return;

            _currentFilename = source._currentFilename;
            _title = source._title;
            _contentId = source._contentId;
            _ps3SystemVersion = source._ps3SystemVersion;
            _contentType = source._contentType;
            _klicense = source._klicense;
            _drmType = source._drmType;
            _appVer = source._appVer;
            _pkgVer = source._pkgVer;
            _autoIncrement = source._autoIncrement;
            _logoFilename = source._logoFilename;

            _parentalLockLevel = source._parentalLockLevel;
            _category = source._category;
            _videoResolution = source._videoResolution;
            _audioFormat = source._audioFormat;
            _pspRemotePlayCodec = source._pspRemotePlayCodec;

            _rootdir = new VirtualDirectory(source._rootdir);
        }

        #region Dispose Implementation

        ~PS3PackageFile()
        {
            Dispose(false);
        }

        bool disposed = false;
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources.
                }

                // There are no unmanaged resources to release, but
                // if we add them, they need to be released here.
            }

            disposed = true;

            // If it is available, make the call to the
            // base class's Dispose(Boolean) method
            // base.Dispose(disposing);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion Dispose Implementation

        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }

        public string ContentId
        {
            get { return _contentId; }
            set { _contentId = value; }
        }

        public string KLicense
        {
            get { return _klicense; }
            set { _klicense = value; }
        }

        public PS3DrmType DrmType
        {
            get { return _drmType; }
            set { _drmType = value; }
        }
        
        public string AppVer
        {
            get { return _appVer; }
            set { _appVer = value; }
        }

        public string PkgVer
        {
            get { return _pkgVer; }
            set { _pkgVer = value; }
        }

        public bool AutoIncrement
        {
            get { return _autoIncrement; }
            set { _autoIncrement = value; }
        }

        public string LogoFilename
        {
            get { return _logoFilename; }
            set { _logoFilename = value; }
        }

        public PS3ContentType ContentType
        {
            get { return _contentType; }
            set { _contentType = value; }
        }

        #region Param.SFO properties

        public string Ps3SystemVersion
        {
            get { return _ps3SystemVersion; }
            set { _ps3SystemVersion = value; }
        }

        public int ParentalLockLevel
        {
            get { return _parentalLockLevel; }
            set { _parentalLockLevel = value; }
        }

        public PS3Category Category
        {
            get { return _category; }
            set { _category = value; }
        }

        public Set<PS3VideoResolution> VideoResolution
        {
            get { return _videoResolution; }
            set { _videoResolution = value; }
        }

        public Set<PS3AudioFormat> AudioFormat
        {
            get { return _audioFormat; }
            set { _audioFormat = value; }
        }

        public Set<PSPRemotePlayCodec> PspRemotePlayCodec
        {
            get { return _pspRemotePlayCodec; }
            set { _pspRemotePlayCodec = value; }
        }

        #endregion Param.SFO properties

        public string Filename
        {
            get { return _currentFilename; }
        }

        public VirtualDirectory RootDir
        {
            get { return _rootdir; }
        }

        #region File management routines

        private void LoadFile(string filename)
        {
            // If it's an empty file, just create a clean file and continue
            FileInfo fi = new FileInfo(filename);
            if (fi.Length == 0)
                return;


            XmlDocument xml = new XmlDocument();
            xml.Load(filename);

            XmlNode rootnode = xml.GetElementsByTagName("package")[0];

            _title = GetXmlNodeString(rootnode, "Title", "");
            _contentId = GetXmlNodeString(rootnode, "ContentId", "");
            _ps3SystemVersion = GetXmlNodeString(rootnode, "Ps3SystemVersion", "1.92");
            _contentType = (PS3ContentType)GetXmlNodeInt(rootnode, "ContentType", (int)PS3ContentType.GameData);
            _klicense = GetXmlNodeString(rootnode, "KLicense", _klicense);
            _drmType = (PS3DrmType)GetXmlNodeInt(rootnode, "DrmType", (int)PS3DrmType.Free);
            _appVer = GetXmlNodeString(rootnode, "appver", "01.01");
            _pkgVer = GetXmlNodeString(rootnode, "pkgver", "01.01");
            _autoIncrement = GetXmlNodeBool(rootnode, "AutoIncrementVersion", false);

            _logoFilename = RelativePathHelper.ReadPath(rootnode, "logo", filename);

            _parentalLockLevel = GetXmlNodeInt(rootnode, "ParentalLockLevel", 0);
            _category = (PS3Category)GetXmlNodeInt(rootnode, "Category", (int)PS3Category.HardDiskGame);
            _videoResolution = GetXmlNodeInt(rootnode, "VideoResolution", 0);
            _audioFormat = GetXmlNodeInt(rootnode, "AudioFormat", 0);
            _pspRemotePlayCodec = GetXmlNodeInt(rootnode, "PspRemotePlayCodec", 0);

            XmlNodeList directorynodes = rootnode.SelectNodes("Directory");
            if (directorynodes.Count > 0)
            {
                _rootdir = ReadVirtualDirectory(directorynodes[0]);
            }

            _currentFilename = filename;
        }

        public void SaveFile(string filename)
        {
            XmlDocument xml = new XmlDocument();

            XmlElement rootnode = xml.CreateElement("package");
            xml.AppendChild(rootnode);

            SetXmlNode(rootnode, "Title", _title);
            SetXmlNode(rootnode, "ContentId", _contentId);
            SetXmlNode(rootnode, "Ps3SystemVersion", _ps3SystemVersion);
            SetXmlNode(rootnode, "ContentType", ((int)_contentType));
            SetXmlNode(rootnode, "DrmType", ((int)_drmType));
            SetXmlNode(rootnode, "KLicense", _klicense);
            SetXmlNode(rootnode, "appver", _appVer);
            SetXmlNode(rootnode, "pkgver", _pkgVer);
            SetXmlNode(rootnode, "AutoIncrementVersion", _autoIncrement);
            // SetXmlNode(rootnode, "logo", _logoFilename);
            RelativePathHelper.SavePath(rootnode, "logo", _logoFilename, filename);

            SetXmlNode(rootnode, "ParentalLockLevel", _parentalLockLevel);
            SetXmlNode(rootnode, "Category", (int)_category);

            SetXmlNode(rootnode, "VideoResolution", (int)_videoResolution);
            SetXmlNode(rootnode, "AudioFormat", (int)_audioFormat);
            SetXmlNode(rootnode, "PspRemotePlayCodec", (int)_pspRemotePlayCodec);


            SaveVirtualDir(rootnode, _rootdir, filename);

            if (File.Exists(filename))
                File.Delete(filename);

            xml.Save(filename);

            _currentFilename = filename;
        }

        private VirtualDirectory ReadVirtualDirectory(XmlNode directorynode)
        {
            string dirname = GetXmlAttributeString(directorynode, "Name", "");
            if (string.IsNullOrEmpty(dirname))
                dirname = "Unknown";

            VirtualDirectory result = new VirtualDirectory(dirname);


            XmlNodeList childdirectorynodes = directorynode.SelectNodes("Directory");
            foreach (XmlNode childdirnode in childdirectorynodes)
            {
                VirtualDirectory childdir = ReadVirtualDirectory(childdirnode);
                result.Directories.Add(childdir);
            }

            XmlNodeList childfilenodes = directorynode.SelectNodes("File");
            foreach (XmlNode childfilenode in childfilenodes)
            {
                string displayname = GetXmlAttributeString(childfilenode, "Name", "");
                if (string.IsNullOrEmpty(displayname))
                    displayname = "Unknown";

                bool islocked = GetXmlAttributeBool(childfilenode, "Locked", false);
                string sourcefilename = GetXmlAttributeString(childfilenode, "SourceFilename", "");
                string relativefilename = GetXmlAttributeString(childfilenode, "RelativeFilename", "");

                VirtualFile childfile = new VirtualFile(displayname, islocked, sourcefilename, relativefilename);
                result.Files.Add(childfile);
            }

            return result;
        }

        private void SaveVirtualDir(XmlElement parentnode, VirtualDirectory directory, string targetpath)
        {
            XmlDocument xml = parentnode.OwnerDocument;

            XmlElement directorynode = xml.CreateElement("Directory");
            parentnode.AppendChild(directorynode);
            SetXmlAttribute(directorynode, "Name", directory.Name);

            foreach (VirtualDirectory subdir in directory.Directories)
            {
                SaveVirtualDir(directorynode, subdir, targetpath);
            }

            foreach (VirtualFile subfile in directory.Files)
            {
                XmlElement filenode = xml.CreateElement("File");
                directorynode.AppendChild(filenode);

                SetXmlAttribute(filenode, "Name", subfile.Name);
                SetXmlAttribute(filenode, "Locked", subfile.Locked);
                SetXmlAttribute(filenode, "SourceFilename", subfile.SourceFilename);
                SetXmlAttribute(filenode, "RelativeFilename", subfile.GetRelativePath(targetpath));
            }
        }

        private void SetXmlNode(XmlElement node, string valuename, object value)
        {
            if (value == null)
                value = "";

            if (value is bool)
            {
                bool b = (bool)value;

                if (b) value = "Yes";
                else value = "No";
            }

            XmlElement childnode = node.OwnerDocument.CreateElement(valuename.ToLower());
            childnode.InnerText = value.ToString();
            node.AppendChild(childnode);
        }

        private void SetXmlAttribute(XmlElement node, string valuename, object value)
        {
            if (value == null)
                value = "";

            if (value is bool)
            {
                bool b = (bool)value;

                if (b) value = "Yes";
                else value = "No";
            }

            node.SetAttribute(valuename, value.ToString());
        }

        private string GetXmlNodeString(XmlNode node, string valuename, string defaultvalue)
        {
            XmlNodeList childnodes = node.SelectNodes(valuename.ToLower());

            if (childnodes.Count == 0)
                return defaultvalue;

            return childnodes[0].InnerText;
        }

        private bool GetXmlNodeBool(XmlNode node, string valuename, bool defaultvalue)
        {
            string str = GetXmlNodeString(node, valuename, "").ToUpper();

            if (str == "YES")
                return true;
            if (str == "NO")
                return false;

            return defaultvalue;
        }

        private int GetXmlNodeInt(XmlNode node, string valuename, int defaultvalue)
        {
            string str = GetXmlNodeString(node, valuename, "").ToUpper();

            int result;
            if (int.TryParse(str, out result))
                return result;

            return defaultvalue;
        }

        private string GetXmlAttributeString(XmlNode node, string valuename, string defaultvalue)
        {
            XmlElement nodeelement = (XmlElement)node;
            if (!nodeelement.HasAttribute(valuename))
                return defaultvalue;

            return nodeelement.GetAttribute(valuename);
        }

        private bool GetXmlAttributeBool(XmlNode node, string valuename, bool defaultvalue)
        {
            string str = GetXmlAttributeString(node, valuename, "").ToUpper();

            if (str == "YES")
                return true;
            if (str == "NO")
                return false;

            return defaultvalue;
        }

        private int GetXmlAttributeInt(XmlNode node, string valuename, int defaultvalue)
        {
            string str = GetXmlAttributeString(node, valuename, "").ToUpper();

            int result;
            if (int.TryParse(str, out result))
                return result;

            return defaultvalue;
        }

        #endregion File management routines
    }
}
